
import FruitItemView from "./FruitItemView";
import RandomHelper from "../../cfw/tools/RandomHelper";
import PhysicsHelper from "./PhysicsHelper";
// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
const EPSILON = 0.1;
const POINT_SQR_EPSILON = 5;

function compare(a: cc.PhysicsRayCastResult, b: cc.PhysicsRayCastResult) {
    if (a.fraction > b.fraction) {
        return 1;
    } else if (a.fraction < b.fraction) {
        return -1;

    }
    return 0;
}

function equals(a, b, epsilon?) {
    epsilon = epsilon === undefined ? EPSILON : epsilon;
    return Math.abs(a - b) < epsilon;
}

function equalsVec2(a, b, epsilon) {
    return equals(a.x, b.x, epsilon) && equals(a.y, b.y, epsilon);
}

function pointInLine(point, a, b) {
    return cc.Intersection.pointLineDistance(point, a, b, true) < 1;
}
const { ccclass, property } = cc._decorator;

@ccclass
export default class CuttingObjects extends cc.Component {

    ctx: cc.Graphics = null;

    @property([cc.Prefab])
    item: cc.Prefab[] = [];


    @property
    createCount: number = 10;//创建个数

    @property
    duration: number = 1;//间隔时间

    @property({
        type: cc.Component.EventHandler,
        displayName: "回调函数"
    })
    callback = new cc.Component.EventHandler();
    
    protected touchPoint: cc.Vec2 = null;
    protected touchStartPoint: cc.Vec2 = null;
    protected r1: cc.PhysicsRayCastResult[] = [];
    protected r2: cc.PhysicsRayCastResult[] = [];
    protected results: cc.PhysicsRayCastResult[] = [];
    protected touching: boolean = false;

    protected activeItems: FruitItemView[] = []


    protected isFinish: boolean = false;
    onDestroy() {
        // 
        // for (let index = 0; index < this.activeItems.length; index++) {
        //     const element = this.activeItems[index];
        //     element.node.destroy()
        // }
    }

    // use this for initialization
    onLoad() {
        this.ctx = this.node.getComponent(cc.Graphics)
        var canvas = this.node;
        canvas.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
        canvas.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this);
        canvas.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this);

        // PhysicsHelper.setDebug(true)

    }

    onEnable() {
        PhysicsHelper.open(cc.v2(0, -100))
        this.gameStart()
    }

    gameStart() {
        cc.log(' gameStart ')
        if (this.createCount > 0) {
            this.createItem()
            // if (this.createCount > 0) {
            cc.tween(this.node).to(this.duration, {}).call(() => {
                if (this.createCount > 0) {
                    this.gameStart()
                } else {
                    if (this.callback) {
                        this.isFinish = true;
                        this.callback.emit([])
                        this.callback = null;
                    }
                }

            }).start()
        } else {

        }
    }

    createItem() {
        this.createCount--;
        let index = RandomHelper.random(0, this.item.length)
        let node = cc.instantiate(this.item[index])
        let width = RandomHelper.random(50, this.node.width - 50)
        let pos = cc.v2(width, 0)
        node.setPosition(this.node.convertToNodeSpaceAR(pos))
        node.parent = this.node;

        let fruit: FruitItemView = node.getComponent(FruitItemView)
        if (fruit) {
            fruit.init()
            fruit.setGravityScale(9)
            if (pos.x > (this.node.width / 2)) {
                fruit.move(RandomHelper.random(10, 30))
            } else {
                fruit.move(RandomHelper.random(-30, -10))
            }
            this.activeItems.push(fruit)
        }
    }

    onTouchStart(event) {
        if (this.isFinish) {
            return;
        }
        this.touching = true;
        this.r1 = this.r2 = this.results = null;
        this.touchStartPoint = this.touchPoint = cc.v2(event.touch.getLocation());
    }

    onTouchMove(event) {
        // this.touchPoint = cc.v2(event.touch.getLocation());
    }

    onTouchEnd(event) {
        if (this.isFinish) {
            return;
        }
        this.touchPoint = cc.v2(event.touch.getLocation());
        // cc.log('onTouchEnd  point ', this.touchPoint)
        if (this.touching) {
            this.recalcResults(this.touchStartPoint, this.touchPoint);
        };

        this.touching = false;

        let point = cc.v2(event.touch.getLocation());
        this.cutting(point)
    }

    cutting(point: cc.Vec2) {
        // cc.log('onTouchEnd  point ', point)
        if (equals(this.touchStartPoint.sub(point).magSqr(), 0)) return;

        // recalculate fraction, make fraction from one direction
        this.r2.forEach(r => {
            r.fraction = 1 - r.fraction;
        });

        let results: cc.PhysicsRayCastResult[] = this.results;

        let pairs: cc.PhysicsRayCastResult[][] = [];

        for (let i = 0; i < results.length; i++) {
            let find = false;
            let result: cc.PhysicsRayCastResult = results[i];

            for (let j = 0; j < pairs.length; j++) {
                let pair: cc.PhysicsRayCastResult[] = pairs[j];
                if (pair[0] && result.collider === pair[0].collider) {
                    find = true;

                    // one collider may contains several fixtures, so raycast may through the inner fixture side
                    // we need remove them from the result
                    let r = pair.find((r) => {
                        return r.point.sub(result.point).magSqr() <= POINT_SQR_EPSILON;
                    });

                    if (r) {
                        pair.splice(pair.indexOf(r), 1);
                    }
                    else {
                        pair.push(result);
                    }

                    break;
                }
            }

            if (!find) {
                pairs.push([result]);
            }
        }

        for (let i = 0; i < pairs.length; i++) {
            let pair: cc.PhysicsRayCastResult[] = pairs[i];
            if (pair.length < 2) {
                continue;
            }

            // sort pair with fraction
            pair = pair.sort(compare);

            let splitResults: any[][] = [];

            // first calculate all results, not split collider right now
            for (let j = 0; j < (pair.length - 1); j += 2) {
                let r1: cc.PhysicsRayCastResult = pair[j];
                let r2: cc.PhysicsRayCastResult = pair[j + 1];

                if (r1 && r1.collider && r2) {
                    this.split(r1.collider, r1.point, r2.point, splitResults);
                }
            }

            if (splitResults.length <= 0) {
                continue;
            }

            let collider = pair[0].collider as cc.PhysicsPolygonCollider;

            let maxPointsResult;
            for (let j = 0; j < splitResults.length; j++) {
                let splitResult = splitResults[j];

                for (let k = 0; k < splitResult.length; k++) {
                    if (typeof splitResult[k] === 'number') {
                        splitResult[k] = collider.points[splitResult[k]];
                    }
                }

                if (!maxPointsResult || splitResult.length > maxPointsResult.length) {
                    maxPointsResult = splitResult;
                }
            }

            if (maxPointsResult.length < 3) {
                continue;
            }


            let fruit: FruitItemView = collider.node.getComponent(FruitItemView)
            if (fruit) {
                fruit.setColliderPoints(maxPointsResult)
                fruit.setMaskPoints(maxPointsResult)

            }


            let body = collider.body;

            for (let j = 0; j < splitResults.length; j++) {
                let splitResult = splitResults[j];

                if (splitResult.length < 3) continue;
                if (splitResult == maxPointsResult) continue;

                // create new body
                let node = cc.instantiate(this.item[fruit.index])
                let pos = cc.v2(0, 0)
                body.getWorldPosition(pos);
                node.setPosition(this.node.convertToNodeSpaceAR(pos))

                node.angle = body.getWorldRotation();
                node.parent = this.node;

                let item: FruitItemView = node.getComponent(FruitItemView)
                if (item) {
                    item.init()
                    item.setColliderPoints(splitResult)
                    item.setMaskPoints(splitResult)
                    item.setLinearVelocity(fruit.getLinearVelocity())
                    item.setAngularVelocity(fruit.getAngularVelocity())
                    item.setGravityScale(fruit.getGravityScale())
                    this.activeItems.push(item)
                }
            }

        }
    }

    split(collider, p1, p2, splitResults) {
        let body = collider.body;
        let points = collider.points;
        if (!points) {
            return;
        }
        p1 = body.getLocalPoint(p1);
        p2 = body.getLocalPoint(p2);


        let newSplitResult1 = [p1, p2];
        let newSplitResult2 = [p2, p1];

        let index1, index2;
        for (let i = 0; i < points.length; i++) {
            let pp1 = points[i];
            let pp2 = i === points.length - 1 ? points[0] : points[i + 1];

            if (index1 === undefined && pointInLine(p1, pp1, pp2)) {
                index1 = i;
            }
            else if (index2 === undefined && pointInLine(p2, pp1, pp2)) {
                index2 = i;
            }

            if (index1 !== undefined && index2 !== undefined) {
                break;
            }
        }

        // console.log(index1 + ' : ' + index2);

        if (index1 === undefined || index2 === undefined) {
            debugger
            return;
        }

        let splitResult, indiceIndex1 = index1, indiceIndex2 = index2;
        if (splitResults.length > 0) {
            for (let i = 0; i < splitResults.length; i++) {
                let indices = splitResults[i];
                indiceIndex1 = indices.indexOf(index1);
                indiceIndex2 = indices.indexOf(index2);

                if (indiceIndex1 !== -1 && indiceIndex2 !== -1) {
                    splitResult = splitResults.splice(i, 1)[0];
                    break;
                }
            }
        }

        if (!splitResult) {
            splitResult = points.map((p, i) => {
                return i;
            });
        }

        for (let i = indiceIndex1 + 1; i !== (indiceIndex2 + 1); i++) {
            if (i >= splitResult.length) {
                i = 0;
            }

            let p = splitResult[i];
            p = typeof p === 'number' ? points[p] : p;

            if (p.sub(p1).magSqr() < POINT_SQR_EPSILON || p.sub(p2).magSqr() < POINT_SQR_EPSILON) {
                continue;
            }

            newSplitResult2.push(splitResult[i]);
        }

        for (let i = indiceIndex2 + 1; i !== indiceIndex1 + 1; i++) {
            if (i >= splitResult.length) {
                i = 0;
            }

            let p = splitResult[i];
            p = typeof p === 'number' ? points[p] : p;

            if (p.sub(p1).magSqr() < POINT_SQR_EPSILON || p.sub(p2).magSqr() < POINT_SQR_EPSILON) {
                continue;
            }

            newSplitResult1.push(splitResult[i]);
        }

        splitResults.push(newSplitResult1);
        splitResults.push(newSplitResult2);
    }

    recalcResults(startPoint: cc.Vec2, endPoint: cc.Vec2) {
        //

        this.ctx.clear();
        this.ctx.moveTo(startPoint.x, startPoint.y);
        this.ctx.lineTo(endPoint.x, endPoint.y);
        this.ctx.stroke();

        let manager = cc.director.getPhysicsManager();

        // manager.rayCast() method calls this function only when it sees that a given line gets into the body - it doesnt see when the line gets out of it.
        // I must have 2 intersection points with a body so that it can be sliced, thats why I use manager.rayCast() again, but this time from B to A - that way the point, at which BA enters the body is the point at which AB leaves it!
        let r1: cc.PhysicsRayCastResult[] = manager.rayCast(startPoint, endPoint, cc.RayCastType.All);
        let r2: cc.PhysicsRayCastResult[] = manager.rayCast(endPoint, startPoint, cc.RayCastType.All);

        let results: cc.PhysicsRayCastResult[] = r1.concat(r2);
        // cc.log(' results length ', results.length)
        for (let i = 0; i < results.length; i++) {
            let p = results[i].point;
            this.ctx.circle(p.x, p.y, 20);
        }
        this.ctx.fill();

        this.r1 = r1;
        this.r2 = r2;
        this.results = results;
    }

    // called every frame, uncomment this function to activate update callback
    update(dt) {
        // body maybe moving, need calc raycast results in update
        if (this.touching) {
            this.recalcResults(this.touchStartPoint, this.touchPoint);
        }

        this.checkOut()
    }
    protected wPos: cc.Vec2 = cc.v2(0, 0)
    checkOut() {
        if (this.activeItems.length > 0) {
            let list: FruitItemView[] = []
            for (let index = 0; index < this.activeItems.length; index++) {
                const element = this.activeItems[index];
                element.getWorldPosition(this.wPos)
                if (this.wPos.x < 0 || this.wPos.x > this.node.width || this.wPos.y < 0 || this.wPos.y > this.node.height) {
                    list.push(element)
                    // cc.log(' check out --------------------- ')
                }
            }
            for (let index = 0; index < list.length; index++) {
                const element = list[index];
                element.sync()
                let i = this.activeItems.indexOf(element)
                this.activeItems.splice(i, 1)
                // element.setRigidBodyType(cc.RigidBodyType.Static)
                element.node.destroy()
            }
        }


    }
}
